﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Gallio.Framework.Assertions;
using Gallio.Model;
using MbUnit.Framework;

namespace Quail
{
    /// <summary>
    /// The task assertion class.
    /// </summary>
    public class TaskAssert
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="TaskAssert"/> class.
        /// </summary>
        /// <param name="task">The task.</param>
        public TaskAssert(Task task)
        {
            ActiveTask = task;
        }

        /// <summary>
        /// Gets the active task.
        /// </summary>
        public Task ActiveTask
        {
            get; private set;
        }

        /// <summary>
        /// Determines whether the specified value is true, if it is not, the Assertion fails and logs an error
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="value">the value to check.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void IsTrue(LogStatus statusToLog, bool value, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.IsTrue(value, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines whether the specified condition is true, if it is not, the Assertion fails and logs an error
        /// </summary>
        /// <param name="condition">condition to check.</param>
        /// <param name="message">message to log on error, description of the assert condition recommended</param>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="messageArgs">The message args.</param>
        public void IsTrue(bool condition, string message, LogStatus statusToLog = LogStatus.Fail, params string[] messageArgs)
        {
            try
            {
                Assert.IsTrue(condition, message, messageArgs);
                AssertPass(message, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines whether the specified value is false, if it is not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="value">the value to check.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void IsFalse(LogStatus statusToLog, bool value, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.IsFalse(value, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines whether the specified condition is false, if it is not, the Assertion fails and logs an error
        /// </summary>
        /// <param name="condition">condition to check.</param>
        /// <param name="message">message to log if the assertion  fails, description of the assert condition recommended</param>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="messageArgs">The message args.</param>
        public void IsFalse(bool condition, string message, LogStatus statusToLog = LogStatus.Fail, params string[] messageArgs)
        {
            try
            {
                Assert.IsFalse(condition, message, messageArgs);
                AssertPass(message, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines whether the specified value is null, if it is, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="value">the value to check.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void IsNull(LogStatus statusToLog, object value, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.IsNull(value, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines whether the specified value is not null, if it is not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="value">the value to check.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void IsNotNull(LogStatus statusToLog, object value, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.IsNotNull(value, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Causes a failure to log automatically
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="messageFormat">The message format.</param>
        public void FailAssert(LogStatus statusToLog, string messageFormat)
        {
            AssertFailed(statusToLog, messageFormat);
        }

        /// <summary>
        /// Causes a failure to log automatically
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void FailAssert(LogStatus statusToLog, string messageFormat, params string[] messageArgs)
        {
            AssertFailed(statusToLog, string.Format(messageFormat, messageArgs));
        }

        /// <summary>
        /// Determines that the actualValue is greater than the expectedValue, if it is not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="expectedValue">the expected value.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void GreaterThan<T>(LogStatus statusToLog, T actualValue, T expectedValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.GreaterThan(actualValue, expectedValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue is greater than or equal to the expectedValue, if it is not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="expectedValue">the expected value.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void GreaterThanOrEqualTo<T>(LogStatus statusToLog, T actualValue, T expectedValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.GreaterThanOrEqualTo(actualValue, expectedValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue is equal to the expectedValue, if it is not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="expectedValue">the expected value.</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void AreEqual<T>(LogStatus statusToLog, T expectedValue, T actualValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.AreEqual(expectedValue, actualValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue is not equal to the expectedValue, if it is not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="expectedValue">the expected value.</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void AreNotEqual<T>(LogStatus statusToLog, T expectedValue, T actualValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.AreNotEqual(expectedValue, actualValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue is less than the expectedValue, if it is not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="expectedValue">the expected value.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void LessThan<T>(LogStatus statusToLog, T actualValue, T expectedValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.LessThan(actualValue, expectedValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue is less than or equal to the expectedValue, if it is not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="expectedValue">the expected value.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void LessThanOrEqualTo<T>(LogStatus statusToLog, T actualValue, T expectedValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.LessThanOrEqualTo(actualValue, expectedValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue is between the minimum and maxium values, if it is not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="minValue">the miniumum value.</param>
        /// <param name="maxValue">the maximum value.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void Between<T>(LogStatus statusToLog, T actualValue, T minValue, T maxValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.Between(actualValue, minValue, maxValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the expectedValue is in the enumeration, if it is not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="enumeration">the enumeration of values.</param>
        /// <param name="expectedValue">the value to be found in the enumeration.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void Contains<T>(LogStatus statusToLog, IEnumerable<T> enumeration, T expectedValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.Contains(enumeration, expectedValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the expectedValue is in the actualValue, if it is not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="expectedValue">the value to be found.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void Contains(LogStatus statusToLog, string actualValue, string expectedValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.Contains(actualValue, expectedValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the expectedValue is not in the enumeration, if it is, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="enumeration">the enumeration of values.</param>
        /// <param name="expectedValue">the value to be found in the enumeration.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void DoesNotContain<T>(LogStatus statusToLog, IEnumerable<T> enumeration, T expectedValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.DoesNotContain(enumeration, expectedValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the expectedValue is not in the actualValue, if it is, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="expectedValue">the value to be found.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void DoesNotContain(LogStatus statusToLog, string actualValue, string expectedValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.DoesNotContain(actualValue, expectedValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue starts with the expectedValue, if it does not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="expectedValue">the value to be found.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void StartsWith(LogStatus statusToLog, string actualValue, string expectedValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.StartsWith(actualValue, expectedValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue ends with the expectedValue, if it does not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="expectedValue">the value to be found.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void EndsWith(LogStatus statusToLog, string actualValue, string expectedValue, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.EndsWith(actualValue, expectedValue, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue matches the regex, if it does not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="regex">The regular expression to match.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void Like(LogStatus statusToLog, string actualValue, Regex regex, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.Like(actualValue, regex, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue matches the regexPattern, if it does not, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="regexPattern">The regular expression pattern to match.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void Like(LogStatus statusToLog, string actualValue, string regexPattern, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.NotLike(actualValue, regexPattern, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue does not match the regex, if it does, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="regex">The regular expression to match.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void NotLike(LogStatus statusToLog, string actualValue, Regex regex, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.NotLike(actualValue, regex, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that the actualValue does not match the regexPattern, if it does, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="actualValue">the actual value.</param>
        /// <param name="regexPattern">The regular expression pattern to match.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void NotLike(LogStatus statusToLog, string actualValue, string regexPattern, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.NotLike(actualValue, regexPattern, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        /// <summary>
        /// Determines that at least one element in the sequence matches the specified condition, if it does, the Assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="values">the list of values.</param>
        /// <param name="predicate">The condition that must return true for at least one element in the list.</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void Exists<T>(LogStatus statusToLog, IEnumerable<T> values, Predicate<T> predicate, string messageFormat, params string[] messageArgs)
        {
            try
            {
                Assert.Exists(values, predicate, messageFormat, messageArgs);
                AssertPass(messageFormat, messageArgs);
            }
            catch (AssertionException ex)
            {
                AssertFailed(statusToLog, ex.Message);
            }
        }

        private void AssertPass(string format, string[] strings)
        {
            ActiveTask.AddLog(LogStatus.Pass, format, strings);
        }

        /// <summary>
        /// Terminates the test.
        /// </summary>
        /// <param name="outcome">the test outcome</param>
        /// <param name="messageFormat">The message format.</param>
        /// <param name="messageArgs">The message args.</param>
        public void Terminate(TestOutcome outcome, string messageFormat, params string[] messageArgs)
        {
            Assert.Terminate(outcome, messageFormat, messageArgs);
        }

        /// <summary>
        /// Called when an assertion fails.
        /// </summary>
        /// <param name="statusToLog">The status code to log if the assertion fails</param>
        /// <param name="message">The message to log</param>
        private void AssertFailed(LogStatus statusToLog, string message)
        {
            ActiveTask.AddLog(statusToLog, message);
        }
    }
}
